﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Input;

namespace LilTmSim
{
	public class GridCanvas : Canvas
	{
		public delegate void StateNodeCollectionResult(StateNodeCollection result);
		public event StateNodeCollectionResult OnStateNodesSelected;

		/// <summary>
		/// IsGridVisible is a Dependency Property.
		/// </summary>
		public bool IsGridVisible
		{
			get { return (bool)GetValue(IsGridVisibleProperty); }
			set { SetValue(IsGridVisibleProperty, value); }
		}
		public static readonly DependencyProperty IsGridVisibleProperty =
			DependencyProperty.Register("IsGridVisible", typeof(bool), typeof(GridCanvas), new UIPropertyMetadata(true, new PropertyChangedCallback(IsGridVisibleChanged)));

		static void IsGridVisibleChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			RedrawCanvas(o as Canvas);
		}

		/// <summary>
		/// GridColor is a Dependency Property.
		/// </summary>
		public Brush GridColor
		{
			get { return (Brush)GetValue(GridColorProperty); }
			set { SetValue(GridColorProperty, value); }
		}
		public static readonly DependencyProperty GridColorProperty =
			DependencyProperty.Register("GridColor", typeof(Brush), typeof(GridCanvas), new UIPropertyMetadata(Brushes.LightPink, new PropertyChangedCallback(GridColorChanged)));

		static void GridColorChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			RedrawCanvas(o as Canvas);
		}

		/// <summary>
		/// Gets or sets the spacing between the grid lines. GridSize is a Dependency Property.
		/// </summary>
		public double GridSize
		{
			get { return (double)GetValue(GridSizeProperty); }
			set { SetValue(GridSizeProperty, value); }
		}
		public static readonly DependencyProperty GridSizeProperty =
			DependencyProperty.Register("GridSize", typeof(double), typeof(GridCanvas), new UIPropertyMetadata(25.0, new PropertyChangedCallback(GridSizeChanged)));

		static void GridSizeChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			RedrawCanvas(o as Canvas);
		}

		/// <summary>
		/// Gets or sets the thickness of the lines in the grid. GridThickness is a Dependency Property.
		/// </summary>
		public double GridThickness
		{
			get { return (double)GetValue(GridThicknessProperty); }
			set { SetValue(GridThicknessProperty, value); }
		}
		public static readonly DependencyProperty GridThicknessProperty =
			DependencyProperty.Register("GridThickness", typeof(double), typeof(GridCanvas), new UIPropertyMetadata(0.5, new PropertyChangedCallback(GridThicknessChanged)));

		static void GridThicknessChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			RedrawCanvas(o as Canvas);
		}

		public GridCanvas()
		{
			this.MouseDown += new System.Windows.Input.MouseButtonEventHandler(GridCanvas_MouseDown);		
		}

		void GridCanvas_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
		{
			if (e.LeftButton == MouseButtonState.Pressed && e.ClickCount == 1)
			{
				if (Mouse.DirectlyOver == this)
				{
					this.CaptureMouse();
					Mouse.AddMouseMoveHandler(this as DependencyObject, canvas_SelectorMove);
					Mouse.AddMouseUpHandler(this as DependencyObject, canvas_SelectorEnd);
					selectStart = Mouse.GetPosition(this as IInputElement);
					selectEnd = selectStart;
				}
			}
		}

		Point selectStart = new Point(-1, -1);
		Point selectEnd = new Point(-1, -1);
		void canvas_SelectorMove(object sender, MouseEventArgs e)
		{
			selectEnd = Mouse.GetPosition(this as IInputElement);
			this.InvalidateVisual();
		}

		void canvas_SelectorEnd(object sender, MouseButtonEventArgs e)
		{
			Mouse.RemoveMouseMoveHandler(this as DependencyObject, canvas_SelectorMove);
			Mouse.RemoveMouseUpHandler(this as DependencyObject, canvas_SelectorEnd);
			

			this.ReleaseMouseCapture();
			this.InvalidateVisual();

			double right = (selectStart.X >= selectEnd.X ? selectStart.X : selectEnd.X);
			double left = (selectStart.X < selectEnd.X ? selectStart.X : selectEnd.X);
			double bottom = (selectStart.Y >= selectEnd.Y ? selectStart.Y : selectEnd.Y);
			double top = (selectStart.Y < selectEnd.Y ? selectStart.Y : selectEnd.Y);
			selectStart = new Point(-1, -1);
			selectEnd = new Point(-1, -1);
			
			StateNodeCollection selectedStates = new StateNodeCollection();
			foreach (StateNode state in Children.OfType<StateNode>())
			{
				if (IsPointInsideBox(state.X, state.Y, top, right, bottom, left))
				{
					selectedStates.Add(state);
				}
			}
			if(OnStateNodesSelected != null)
				OnStateNodesSelected(selectedStates);			
		}

		private bool IsPointInsideBox(double ptX, double ptY, double top, double right, double bottom, double left)
		{
			return ptX > left && ptX < right && ptY > top && ptY < bottom;
		}

		private static void RedrawCanvas(Canvas c)
		{
			c.InvalidateVisual();
		}


		protected override void OnRender(DrawingContext dc)
		{
			base.OnRender(dc);

			DrawGrid(dc);
			DrawSelector(dc);
		}

		private void DrawGrid(DrawingContext dc)
		{
			if (IsGridVisible)
			{
				for (double y = GridSize / 2; y < this.ActualHeight; y += GridSize)
				{
					Point startPoint = new Point(0, y);
					Point endPoint = new Point(this.ActualWidth, y);
					dc.DrawLine(new System.Windows.Media.Pen(GridColor, GridThickness), startPoint, endPoint);
				}

				for (double x = GridSize / 2; x < this.ActualWidth; x += GridSize)
				{
					Point startPoint = new Point(x, 0);
					Point endPoint = new Point(x, this.ActualHeight);
					dc.DrawLine(new System.Windows.Media.Pen(GridColor, GridThickness), startPoint, endPoint);
				}
			}
		}

		private void DrawSelector(DrawingContext dc)
		{
			if (Selecting)
			{
				dc.DrawRectangle(new SolidColorBrush(Color.FromArgb(75, 0, 0, 255)), new Pen(Brushes.Blue, 1.5), SelectionRect);
			}
		}

		private Rect SelectionRect
		{
			get
			{
				double maxX = selectStart.X > selectEnd.X ? selectStart.X : selectEnd.X;
				double minX = selectStart.X < selectEnd.X ? selectStart.X : selectEnd.X;
				double maxY = selectStart.Y > selectEnd.Y ? selectStart.Y : selectEnd.Y;
				double minY = selectStart.Y < selectEnd.Y ? selectStart.Y : selectEnd.Y;
				return new Rect(minX, minY, maxX - minX, maxY - minY);
			}
		}

		private bool Selecting { get { return selectStart != selectEnd; } }
	}
}
